package com.heo.queue.task;

import java.net.InetSocketAddress;
import java.text.DateFormat;
import java.util.Date;

import org.apache.log4j.Logger;

import com.heo.pojo.DeviceSendPacketHistoryEntity;
import com.heo.protocol.packet.BasePacket;
import com.heo.protocol.packet.DeviceCommand;
import com.heo.pub.ApplicationContextUtil;
import com.heo.pub.DictEnum;
import com.heo.pub.ServerConfig;
import com.heo.queue.TaskQueue;
import com.heo.server.SocketChannelMap;
import com.heo.service.DeviceSendPacketHistoryService;
import com.heo.util.ByteHelper;

import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;

/**
 * 向设备发送消息的处理线程
 */

public class SendTaskThread implements Runnable
{
  private static Logger log = Logger.getLogger(SendTaskThread.class);
  private static String THREAD_NAME = "[发送指令线程]";

  private static final int SLEEP_TIME = 100;// 每次休息100毫秒

  public SendTaskThread()
  {

  }

  @Override
  public void run()
  {
    try
    {
      sendTask();
    }
    catch (Exception e)
    {
      log.error(e.getMessage(), e);
      log.error(THREAD_NAME + "[推送]线程出现严重异常，已退出运行。");
    }
  }

  public void sendTask() throws Exception
  {
    log.debug(THREAD_NAME + "[推送]线程开始执行。");

    SendTask sendTask = null;
    // 这里不能先判断队列 长度，然后再来取出，在多线程环境下，会造成判断长度不为0，但是实际取出的时候，已经被其他线程取走的情况
    while ((sendTask = TaskQueue.pollSendTask()) != null)
    {
      Thread.sleep(SLEEP_TIME);
      String channelId = null;
      String remoteIpAddress = null;
      String logHeader = null;
      try
      {
        Channel channel = SocketChannelMap.getSocketChannelByIMEI(sendTask.getDeviceInfoEntity().getDeviceImei());
        if (channel == null)
        {
          // 如果设备一直不在线，那么超时还是需要把该请求丢弃掉
          // 判断是否超时，这里表示已经超时
          if (new Date().getTime() > sendTask.getReSendInfo().getExpireTime())
          {
            log.error(logHeader + THREAD_NAME + "[推送]在规定时间内设备一直不在线，该发送任务已被丢弃。");
            continue;
          }

          log.error(logHeader + THREAD_NAME + "[推送]向设备发送指令失败，设备不在线。重新进入队列，等待下次再处理。");

          boolean result = TaskQueue.offerSendTask(sendTask, false);// 进队列时，不需要启动新的处理线程
          if (!result)
          {
            log.error(logHeader + THREAD_NAME + "[推送]向设备发送指令失败，设备不在线。重新放入队列时，进队列失败。该发送任务已被丢弃。");
          }
          continue;
        }

        channelId = channel.id().asShortText();
        remoteIpAddress = ((InetSocketAddress) channel.remoteAddress()).getAddress().getHostAddress();
        logHeader = "[server][" + channelId + "][" + remoteIpAddress + "]";

        // 判断设备是否支持重发
        if (DictEnum.ReSend.NO == sendTask.getDeviceInfoEntity().getResendMode())
        {
          // 不支持重发
          // 发送数据
          sendPacket(channel, sendTask.getBasePacket(), sendTask.getReSendInfo().getReSendCount(), sendTask.getDeviceInfoEntity().getDeviceImei());
          // 继续下一个任务
          continue;
        }

        // 判断指令是否需要重发，目前仅支持启动指令重发，其他指令都不重发
        short commandId = sendTask.getBasePacket().getCommandId();
        if (DeviceCommand.START_DEVICE_REQ != commandId)
        {
          // 不支持重发
          // 发送数据
          sendPacket(channel, sendTask.getBasePacket(), sendTask.getReSendInfo().getReSendCount(), sendTask.getDeviceInfoEntity().getDeviceImei());
          // 继续下一个任务
          continue;
        }

        // 支持重发，开始重发处理
        if (sendTask.getReSendInfo().getReSendCount() == 0)
        {
          // 第一次发送
          sendPacket(channel, sendTask.getBasePacket(), sendTask.getReSendInfo().getReSendCount(), sendTask.getDeviceInfoEntity().getDeviceImei());
          sendTask.setReSend();// 计数器加一，并同时更新上次发送的时间
          TaskQueue.offerSendTask(sendTask, false);// 进队列时，不需要启动新的处理线程,重新进队列，下次会判断是否需要重发
        }
        else
        {
          // 非第一次发送
          String deviceIMEI = sendTask.getDeviceInfoEntity().getDeviceImei();
          Integer sequenceId = sendTask.getBasePacket().getSequenceId();
          boolean isResponseBack = ReSendResponseInfo.isResponseInfo(deviceIMEI, sequenceId);// 判断该发送指令的应答是否已经返回
          if (isResponseBack)
          {
            // 应答包已经返回，不需要再重发。丢弃
            log.error(logHeader + THREAD_NAME + "[推送]设备的应答已经返回，不需要重发。该发送任务已经被丢弃。");
            continue;
          }

          // 判断是否达到间隔重发的时间
          if ((new Date().getTime() - sendTask.getReSendInfo().getLastSendTime()) < ServerConfig.RESEND_INTERVAL)
          {
            // 没到重发时间，重新进队列排队
            TaskQueue.offerSendTask(sendTask, false);// 进队列时，不需要启动新的处理线程
            continue;
          }

          // 判断是否超时，这里表示已经超时
          if (new Date().getTime() > sendTask.getReSendInfo().getExpireTime())
          {
            log.error(logHeader + THREAD_NAME + "[推送]在规定时间内未收到设备应答，该发送任务已被丢弃。");
            continue;
          }

          // 判断重发次数超限
          if (sendTask.getReSendInfo().getReSendCount() > sendTask.getReSendInfo().getMaxReSendCount())
          {
            log.debug(logHeader + "[推送]重发请求已达到最大重发次数，已被丢弃。丢弃内容：" + sendTask.getBasePacket().toString());
            continue;
          }

          // 开始重发
          sendPacket(channel, sendTask.getBasePacket(), sendTask.getReSendInfo().getReSendCount(), sendTask.getDeviceInfoEntity().getDeviceImei());
          sendTask.setReSend();// 计数器加一，并同时更新上次发送的时间
          TaskQueue.offerSendTask(sendTask, false);// 进队列时，不需要启动新的处理线程//
                                                   // 重新进队列，下次会判断是否需要重发
        }
      }
      catch (Exception e)
      {
        // 发送失败，重新进队列发
        boolean result = TaskQueue.offerSendTask(sendTask, false);// 进队列时，不需要启动新的处理线程
        log.error(logHeader + THREAD_NAME + "[推送]向设备发送指令异常，重新放入队列发送。进队列结果：" + result);
        log.error(e.getMessage(), e);
      }
    }
    log.debug(THREAD_NAME + "[推送]线程执行完毕。");
  }

  /**
   * 发送数据包
   * 
   * @param ctx
   * @param packet
   */
  private void sendPacket(Channel sc, BasePacket packet, int sendCount, String imei)
  {
    String channelId = sc.id().asShortText();
    String remoteIpAddress = ((InetSocketAddress) sc.remoteAddress()).getAddress().getHostAddress();
    String logHeader = "[server][" + channelId + "][" + remoteIpAddress + "]";
    log.debug(logHeader + THREAD_NAME + "[推送]向设备<" + imei + ">发送指令。指令内容：" + packet.toString());
    packet.setSendCount((short) sendCount);
    sc.writeAndFlush(packet).addListener(new ChannelFutureListener()
    {
      // 数据发送完毕后，触发本事件
      @Override
      public void operationComplete(ChannelFuture future) throws Exception
      {
        log.debug(logHeader + packet.getPacketName() + "[推送]向设备<" + imei + ">发送指令成功。指令内容：" + packet.toString());
      }
    });
  }
}
